﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml.Linq;

namespace CodeGeneration
{
    class Program
    {
        static string inputFolder = @"C:\Documents and Settings\Shayan\My Documents\Visual Studio 2008\Projects\AntiObesityServices\AntiObesity.Base\EntityDefinations\";
        static string codeFolder = @"C:\Documents and Settings\Shayan\My Documents\Visual Studio 2008\Projects\AntiObesityServices\AntiObesity.Base\";
        static string managersFolder = @"C:\Documents and Settings\Shayan\My Documents\Visual Studio 2008\Projects\AntiObesityServices\AntiObesity.Base\Managers\";
        static void Main(string[] args)
        {
            var xmlFiles = Directory.GetFiles(inputFolder, "*.xml", SearchOption.TopDirectoryOnly);
            foreach (var file in xmlFiles)
            {
                Generate(file);
                GenerateManager(file);
                Console.WriteLine(Path.GetFileName(file));
            }
            Console.ReadKey();
        }

        private static void Generate(string file)
        {
            var doc = XDocument.Load(file);
            var entityName = doc.Root.Element("Name").Value;
            var codeFileName = entityName + ".cs";
            var sb = new StringBuilder();
            GenerateCodeHeader(sb, entityName, Path.GetFileName(file));
            GenerateProperties(sb, doc.Root.Element("Properties").Elements("Property"));
            GenerateCodeTailer(sb);
            File.WriteAllText(Path.Combine(codeFolder, codeFileName), sb.ToString());
        }

        #region Entity
        private static void GenerateCodeHeader(StringBuilder sb, string className, string file)
        {
            sb.AppendFormat("/* AUTO-GENERATED FROM {0} */\n", file);
            sb.AppendLine("using System;");
            sb.AppendLine("using System.Collections.Generic;");
            sb.AppendLine("using System.Linq;");
            sb.AppendLine("using System.Text;");
            sb.AppendLine("");
            sb.AppendLine("namespace AntiObesity.Base");
            sb.AppendLine("{");
            sb.AppendLine("    [Serializable]");
            sb.AppendFormat("    public partial class {0} : Entity\n", className);
            sb.AppendLine("    {");
        }

        private static void GenerateProperties(StringBuilder sb, IEnumerable<XElement> xProperties)
        {
            foreach (var prop in xProperties)
            {
                sb.AppendFormat("        public {0} {1} {{ get; set; }}\n",
                    prop.Element("Type").Value,
                    prop.Element("Name").Value);
            }
        }

        private static void GenerateCodeTailer(StringBuilder sb)
        {
            sb.AppendLine("    }");
            sb.AppendLine("}");
        }
        #endregion

        #region Manager
        private static void GenerateManager(string file)
        {
            var doc = XDocument.Load(file);
            var entityName = doc.Root.Element("Name").Value;
            var managerFileName = entityName + "Manager.cs";
            var sb = new StringBuilder();
            GenerateManagerHeader(sb, entityName, Path.GetFileName(file));
            GenerateManagerMethods(sb,
                entityName,
                doc.Root.Element("Properties").Elements("Property"),
                doc.Root.Element("Sorters").Elements("Sorter"));
            GenerateManagerTailer(sb);
            File.WriteAllText(Path.Combine(managersFolder, managerFileName), sb.ToString());
        }

        private static void GenerateManagerHeader(StringBuilder sb, string entityName, string file)
        {
            sb.AppendFormat("/* AUTO-GENERATED FROM {0} */\n", file);
            sb.AppendLine("using System;");
            sb.AppendLine("using System.Data.SqlClient;");
            sb.AppendLine("using System.Text;");
            sb.AppendLine("using AntiObesity.Base;");
            sb.AppendLine("using System.Collections.Generic;");
            sb.AppendLine("using AntiObesity.DatabaseAccess;");
            sb.AppendLine("using System.Globalization;");
            sb.AppendLine();
            sb.AppendLine("namespace AntiObesity.Base.Managers");
            sb.AppendLine("{");
            sb.AppendFormat("    public static partial class {0}Manager\n", entityName);
            sb.AppendLine("    {");
        }

        private static void GenerateManagerMethods(StringBuilder sb, string entityName, IEnumerable<XElement> properties, IEnumerable<XElement> sorters)
        {
            GenerateGetAllMethod(sb, entityName, sorters);
            GenerateFindByMethods(sb, entityName, properties);
            GenerateExistsMethods(sb, entityName, properties);
            GeneratePersistMethod(sb, entityName, properties);
            GenerateCreatorMethod(sb, entityName, properties);
            GenerateDeleteMethod(sb, entityName);
            GenerateDeleteAllMethod(sb, entityName);
        }

        private static void GenerateDeleteMethod(StringBuilder sb, string entityName)
        {
            var tableName = GetPlurar(entityName);

            sb.AppendFormat(@"
        public static void Delete(string id)
        {{
            using (var conn = DB.Instance.Current.GetConnection())
            {{
                DB.Instance.Current.Execute(conn, String.Format(""DELETE FROM [{0}] WHERE ID='{{0}}'"", id));
            }}
        }}

", tableName);
        }

        private static void GenerateCreatorMethod(StringBuilder sb, string entityName, IEnumerable<XElement> properties)
        {
            var v = GetLocalForm(entityName);

            sb.AppendFormat("        private static {0} Create{0}(SqlDataReader reader)\n", entityName);
            sb.AppendLine("        {");
            sb.AppendFormat("            var {0} = new {1}();\n", v, entityName);
            sb.AppendLine("");
            sb.AppendFormat("            {0}.ID = new Guid(Convert.ToString(reader[\"ID\"]));\n", v);
            foreach (var prop in properties)
            {
                var propName = prop.Element("Name").Value;
                var propType = prop.Element("Type").Value;

                sb.AppendFormat("            {0}.{1} = {2};\n",
                    v,
                    propName,
                    GetConverterCode(
                        propType,
                        String.Format("reader[\"{0}\"]", propName)
                    ));
            }
            sb.AppendLine("");
            sb.AppendFormat("            return {0};\n", v);
            sb.AppendLine("        }");
        }

        private static void GeneratePersistMethod(StringBuilder sb, string entityName, IEnumerable<XElement> properties)
        {
            var v = GetLocalForm(entityName);
            var tableName = GetPlurar(entityName);

            sb.AppendFormat("        public static Guid Persist({0} {1})\n", entityName, v);
            sb.AppendLine("        {");
            sb.AppendLine("            using (var conn = DB.Instance.Current.GetConnection())");
            sb.AppendLine("            {");
            sb.AppendFormat("                if (Exists(conn, {0}.ID))\n", v);
            sb.AppendLine("                {");
            sb.AppendLine("                    var sb = new StringBuilder();");
            sb.AppendFormat("                    sb.Append(\"UPDATE [{0}] SET \");\n", tableName);
            foreach (var prop in properties)
            {
                var propName = prop.Element("Name").Value;
                var propType = prop.Element("Type").Value;
                var isLastProp = prop.ElementsAfterSelf().Count() == 0;
                var comma = isLastProp ? "" : ",";

                if (propType == "DateTime")
                {
                    sb.AppendFormat("                    sb.AppendFormat(\"[{0}]='{{0}}'{2}\", {1}.{0}.ToString(CultureInfo.InvariantCulture));\n", propName, v, comma);
                }
                else if (propType == "bool")
                {
                    sb.AppendFormat("                    sb.AppendFormat(\"[{0}]={{0}}{2}\", {1}.{0} ? 1 : 0);\n", propName, v, comma);
                }
                else if (propType == "String")
                {
                    sb.AppendFormat("                    sb.AppendFormat(\"[{0}]='{{0}}'{2}\", {1}.{0}.Replace(\"'\",\"''\"));\n", propName, v, comma);
                }
                else if (SqlNeedsQuote(propType))
                {
                    sb.AppendFormat("                    sb.AppendFormat(\"[{0}]='{{0}}'{2}\", {1}.{0});\n", propName, v, comma);
                }
                else
                {
                    sb.AppendFormat("                    sb.AppendFormat(\"[{0}]={{0}}{2}\", {1}.{0});\n", propName, v, comma);
                }
            }
            sb.AppendFormat("                    sb.AppendFormat(\" WHERE ID='{{0}}'\", {0}.ID);\n", v);
            sb.AppendLine("                    DB.Instance.Current.Execute(conn, sb.ToString());");
            sb.AppendLine("                }");
            sb.AppendLine("                else");
            sb.AppendLine("                {");
            sb.AppendLine("                    DB.Instance.Current.Execute(conn, String.Format(");
            sb.AppendFormat("                        \"INSERT INTO [{0}] ([ID],", tableName);
            foreach (var prop in properties)
            {
                var propName = prop.Element("Name").Value;
                var propType = prop.Element("Type").Value;
                var isLastProp = prop.ElementsAfterSelf().Count() == 0;

                sb.AppendFormat("[{0}]", propName);
                if (!isLastProp)
                    sb.Append(",");
            }
            sb.Append(") VALUES ('{0}',");
            int i = 1;
            foreach (var prop in properties)
            {
                var propName = prop.Element("Name").Value;
                var propType = prop.Element("Type").Value;
                var isLastProp = prop.ElementsAfterSelf().Count() == 0;

                if (SqlNeedsQuote(propType))
                {
                    sb.AppendFormat("'{{{0}}}'", i);
                }
                else
                {
                    sb.AppendFormat("{{{0}}}", i);
                }
                if (!isLastProp)
                    sb.Append(",");
                i++;
            }
            sb.AppendLine(")\",");
            sb.AppendFormat("                        {0}.ID,\n", v);
            foreach (var prop in properties)
            {
                var propName = prop.Element("Name").Value;
                var propType = prop.Element("Type").Value;
                var isLastProp = prop.ElementsAfterSelf().Count() == 0;

                if (propType == "DateTime")
                {
                    sb.AppendFormat("                        {0}.{1}.ToString(CultureInfo.InvariantCulture)", v, propName);
                }
                else if (propType == "String")
                {
                    sb.AppendFormat("                        {0}.{1}.Replace(\"'\",\"''\")", v, propName);
                }
                else if (propType == "bool")
                {
                    sb.AppendFormat("                        {0}.{1} ? 1 : 0", v, propName);
                }
                else
                {
                    sb.AppendFormat("                        {0}.{1}", v, propName);
                }
                if (!isLastProp)
                    sb.AppendLine(",");
                else
                    sb.AppendLine();
            }
            sb.AppendLine("                    ));");
            sb.AppendLine("                }");
            sb.AppendLine("            }");
            sb.AppendFormat("            return {0}.ID;\n", v);
            sb.AppendLine("        }");
            sb.AppendLine();
        }

        private static void GenerateGetAllMethod(StringBuilder sb, string entityName, IEnumerable<XElement> sorters)
        {
            sb.AppendFormat("        public static List<{0}> GetAll()\n", entityName);
            sb.AppendLine("        {");
            sb.AppendFormat("            var result = new List<{0}>();\n", entityName);
            sb.AppendLine("            using (var conn = DB.Instance.Current.GetConnection())");
            sb.AppendLine("            {");
            sb.AppendFormat("                var reader = DB.Instance.Current.ExecuteForReader(conn, \"SELECT * FROM [{0}] ", GetPlurar(entityName));
            if (sorters.Any())
            {
                sb.Append("ORDER BY ");
                foreach (var sorter in sorters)
                {
                    var property = sorter.Element("Property").Value;
                    var direction = sorter.Element("Direction") == null ? "" : sorter.Element("Direction").Value;
                    var isLast = sorter.ElementsAfterSelf().Count() == 0;

                    sb.AppendFormat("[{0}] {1}", property, direction);
                    if (!isLast)
                        sb.Append(",");
                }
            }
            sb.AppendFormat(" \");\n");
            sb.AppendLine("                while (reader.Read())");
            sb.AppendLine("                {");
            sb.AppendFormat("                    result.Add(Create{0}(reader));\n", entityName);
            sb.AppendLine("                }");
            sb.AppendLine("            }");
            sb.AppendLine("            return result;");
            sb.AppendLine("        }");
            sb.AppendLine();
        }

        private static void GenerateDeleteAllMethod(StringBuilder sb, string entityName)
        {
            sb.AppendLine("        public static void DeleteAll()");
            sb.AppendLine("        {");
            sb.AppendLine("            using (var conn = DB.Instance.Current.GetConnection())");
            sb.AppendLine("            {");
            sb.AppendFormat("                DB.Instance.Current.Execute(conn, \"DELETE FROM [{0}]\");\n", GetPlurar(entityName));
            sb.AppendLine("            }");
            sb.AppendLine("        }");
        }

        private static void GenerateFindByMethods(StringBuilder sb, string entityName, IEnumerable<XElement> properties)
        {
            GenerateFindByMethod(sb, entityName, "ID", "Guid");
            sb.AppendLine();
            foreach (var prop in UniqeProperties(properties))
            {
                var propName = prop.Element("Name").Value;
                var propType = prop.Element("Type").Value;
                GenerateFindByMethod(sb, entityName, propName, propType);
                sb.AppendLine();
            }
        }

        private static void GenerateFindByMethod(StringBuilder sb, string entityName, string propName, string propType)
        {
            var tableName = GetPlurar(entityName);
            var v = GetLocalForm(propName);

            sb.AppendFormat("        public static {0} FindBy{1}({2} {3})\n", entityName, propName, propType, v);
            sb.AppendLine("        {");
            sb.AppendLine("            using (var conn = DB.Instance.Current.GetConnection())");
            sb.AppendLine("            {");
            sb.Append("                var reader = DB.Instance.Current.ExecuteForReader(");
            sb.AppendFormat("conn, String.Format(\"SELECT * FROM [{0}] WHERE [{1}]=", tableName, propName);
            if (SqlNeedsQuote(propType))
                sb.Append("'{0}'");
            else
                sb.Append("{0}");
            sb.AppendFormat("\", {0}{1}));\n", v, propType == "String" ? ".Replace(\"'\",\"''\")" : "");
            sb.AppendLine("                if (reader.Read())");
            sb.AppendLine("                {");
            sb.AppendFormat("                    return Create{0}(reader);\n", entityName);
            sb.AppendLine("                }");
            sb.AppendLine("            }");
            sb.AppendLine("            return null;");
            sb.AppendLine("        }");
        }

        private static void GenerateExistsMethods(StringBuilder sb, string entityName, IEnumerable<XElement> properties)
        {
            GenerateExistsMethod(sb, entityName, "ID", "Guid");
            sb.AppendLine();
            foreach (var prop in UniqeProperties(properties))
            {
                var propName = prop.Element("Name").Value;
                var propType = prop.Element("Type").Value;
                GenerateExistsMethod(sb, entityName, propName, propType);
                sb.AppendLine();
            }
        }

        private static void GenerateExistsMethod(StringBuilder sb, string entityName, string propName, string propType)
        {
            var tableName = GetPlurar(entityName);
            var v = GetLocalForm(propName);

            sb.AppendFormat("        public static bool Exists({0} {1})\n", propType, v);
            sb.AppendLine("        {");
            sb.AppendLine("            using (var conn = DB.Instance.Current.GetConnection())");
            sb.AppendLine("            {");
            sb.AppendFormat("                return Exists(conn, {0});\n", v);
            sb.AppendLine("            }");
            sb.AppendLine("        }");
            sb.AppendLine("");
            sb.AppendFormat("        private static bool Exists(SqlConnection conn, {0} {1})\n", propType, v);
            sb.AppendLine("        {");
            sb.AppendFormat("            var count = DB.Instance.Current.ExecuteForInt(conn, String.Format(\"SELECT Count(*) FROM [{0}] WHERE [{1}]='{{0}}'\", {2}));\n", tableName, propName, v);
            sb.AppendLine("            return count >= 1;");
            sb.AppendLine("        }");
        }

        private static string GetConverterCode(string type, string convertee)
        {
            if (type == "String") return String.Format("Convert.ToString({0})", convertee);
            if (type == "Guid") return String.Format("new Guid(Convert.ToString({0}))", convertee);
            if (type == "float") return String.Format("(float)Convert.ToDouble({0})", convertee);
            if (type == "bool") return String.Format("Convert.ToBoolean({0})", convertee);
            if (type == "DateTime") return String.Format("Convert.ToDateTime({0})", convertee);
            //if (type == "") return String.Format("", convertee);

            throw new NotImplementedException(type);
        }

        private static void GenerateManagerTailer(StringBuilder sb)
        {
            sb.AppendLine("    }");
            sb.AppendLine("}");
        }
        #endregion

        #region Utils
        private static bool SqlNeedsQuote(string type)
        {
            if (type == "String") return true;
            if (type == "Guid") return true;
            if (type == "float") return false;
            if (type == "bool") return false;
            if (type == "DateTime") return true;

            throw new NotImplementedException(type);
        }

        private static string GetPlurar(string entityName)
        {
            if (entityName.EndsWith("y"))
                return entityName.Substring(0, entityName.Length - 1) + "ies";
            return entityName + "s";
        }

        private static string GetLocalForm(string identifier)
        {
            if (identifier.Length <= 2) // ex: ID -> id
                return identifier.ToLower();
            else
                return Char.ToLower(identifier[0]) + identifier.Substring(1); // ex: Name -> name
        }

        private static IEnumerable<XElement> UniqeProperties(IEnumerable<XElement> xProperties)
        {
            return xProperties.Where(x => x.Element("IsUnique") != null && Convert.ToBoolean(x.Element("IsUnique").Value) == true);
        }

        #endregion
    }
}
